#include "nr_net_server_logic.h"
#include <Windows.h>

nr_net_session::nr_net_session(nr_channel_manager &channel_manager)
    : m_session_id(NR_INVALID_ID)
    , m_socket(NULL)
    , m_channel_manager(channel_manager)
{
}

nr_net_session::~nr_net_session()
{
    close();
}

void nr_net_session::stop_device(const nr_device_id_t& device_id)
{
    nr_session_channels::iterator it = m_channels.find(device_id);
    if (it != m_channels.end())
    {
        nr_channel_output *output_channel = it.value();
        Q_ASSERT(output_channel);

        // remove from the list
        m_channels.erase(it);
        
        // destroy the output channel
        m_channel_manager.destroy_output_channel(output_channel);
    }
}

// nr_channel_output interfaces
void nr_net_session::on_data(nr_data *data)
{
    Q_ASSERT(data);
    
    QByteArray byte_array;
    nr_net_helper::object_to_byte_array(*data, byte_array);
    
    nr_net_command_header header;
    header.session_id = m_session_id;
    header.cmd_type = NR_NET_COMMAND_SERVER_DATA;
    header.content_length = byte_array.size();

    int sock = m_socket->socket()->socketDescriptor();

    // Write the header
    int send_len = send(sock, (char*)&header, sizeof(header), 0);
    if (send_len <= 0)
    {
        qDebug() << "Write data header failed!";
    }
    
    // Write the header
    //qint64 len = m_socket->socket()->write((char*)&header, sizeof(header));
    //if (len == -1) // Some error occurred
    //{
    //    qDebug() << "Write data header failed!";

    //    // Should close this session
    //    
    //}
    
    // Should write whole packet
    Q_ASSERT(send_len == sizeof(header));

    // Write the content
    //len = m_socket->socket()->write(byte_array.data(), byte_array.size());
    send_len = send(sock, (char*)byte_array.data(), byte_array.size(), 0);
    if (send_len <= 0)
    {
        qDebug() << "Write data body failed!";
        // Should close this session
    }

    // Should write whole packet
    Q_ASSERT(send_len == byte_array.size());
}

void nr_net_session::on_event(nr_event *evt)
{

}

void nr_net_session::close()
{
    // Destroy each output channel
    foreach(nr_channel_output* output_channel, m_channels)
    {
        m_channel_manager.destroy_output_channel(output_channel);
    }
    
    m_channels.clear();
    m_socket = NULL;
    m_session_id = NR_INVALID_ID;
}

nr_net_server_logic::nr_net_server_logic(nr_object_manager& object_manager)
    : m_object_manager(object_manager)
{
}

nr_net_server_logic::~nr_net_server_logic(void)
{
}

bool nr_net_server_logic::execute(nr_net_socket& socket, nr_net_command_header &request_header, QByteArray &request_byte_array,
    nr_net_command_header &reply_header, QByteArray &reply_byte_array) 
{ 
    nr_object* request = nr_net_helper::create_command_by_type(request_header.cmd_type, true); 
    nr_object* reply = nr_net_helper::create_command_by_type(request_header.cmd_type, false);
    Q_ASSERT(request && reply);
    
    bool is_close_session = false;

    nr_error_t status_code = NR_ERROR_NONE;
    if (request == NULL || reply == NULL)
    {
        reply_header.cmd_type = NR_NET_COMMAND_ERROR;
        reply_header.status_code =NR_ERROR_COMMAND_NOT_SUPPORT;
        goto End;
    }

    nr_net_helper::byte_array_to_object(request_byte_array, *request);
    switch(request_header.cmd_type)
    {
        case NR_NET_COMMAND_LOGIN: 
            status_code = on_login(socket, *request, *reply); break;
        case NR_NET_COMMAND_LOGOUT: 
            status_code = on_logout(socket, *request, *reply); 
            is_close_session = true;
            break;
        case NR_NET_COMMAND_GET_ITEMS:
            status_code = on_get_items(socket, *request, *reply);break;
        case NR_NET_COMMAND_START_DATA:
            status_code = on_start_data(socket, *request, *reply); break;
        case NR_NET_COMMAND_STOP_DATA:
            status_code = on_stop_data(socket, *request, *reply); break;
        case NR_NET_COMMAND_QUERY_DATA:
            status_code = on_query(socket, *request, *reply); break;
        default:
            reply_header.status_code = NR_ERROR_COMMAND_NOT_SUPPORT;
    }
    
    if (NR_SUCCEED(status_code))
    { 
        nr_net_helper::object_to_byte_array(*reply, reply_byte_array);
        reply_header.cmd_type = request_header.cmd_type;
    }
    else
    {
        reply_header.cmd_type = NR_NET_COMMAND_ERROR;
        reply_header.status_code = (qint32)status_code;
    }

End:
    delete request;
    delete reply;

    return is_close_session;
}

nr_error_t nr_net_server_logic::on_login(nr_net_socket &socket, nr_object &request, nr_object &reply)
{
    // 1. Convert the object
    nr_request_login& request_obj = request.object_ref<nr_request_login>(); 
    nr_reply_login& reply_obj = reply.object_ref<nr_reply_login>(); 
    
    // 2. First check the user, ignore it current now

    // Generate a random unique cookie key for security
    reply_obj.cookie() = QUuid::createUuid().toString();

    return NR_ERROR_NONE;
}

nr_error_t nr_net_server_logic::on_logout(nr_net_socket &socket, nr_object &request, nr_object &reply)
{
    // Does not need to remove the session, because the server will remove it then
    
    return NR_ERROR_NONE;
}

nr_error_t nr_net_server_logic::on_get_items(nr_net_socket &socket, nr_object &request, nr_object &reply)
{
    nr_reply_get_items& reply_obj = reply.object_ref<nr_reply_get_items>();
    
    // Copy the items
    reply_obj.root_item() = m_object_manager.device_item_manager().root_item();

    return NR_ERROR_NONE;
}

nr_error_t nr_net_server_logic::on_start_data(nr_net_socket &socket, nr_object &request, nr_object &reply)
{
    nr_request_start_data& request_obj = request.object_ref<nr_request_start_data>();
    nr_reply_start_data& reply_obj = reply.object_ref<nr_reply_start_data>();

    // Find in the existed session or create a new session
    nr_net_session *session = NULL;
    nr_net_sessions::iterator it = m_sessions.find(socket.session_id());
    if (it != m_sessions.end())
    {
        // Session existed, use the existed session
        session  =it.value();
    }
    else
    {
        // Not existed, create a new session
        session = new nr_net_session(m_object_manager.channel_manager());
        session->socket() = &socket; 
        session->session_id() = socket.session_id();

        // Add to the session list anyway, delete it if all channels are failed
        m_sessions.insert(socket.session_id(), session);
    }
    
    Q_ASSERT(session);

    nr_error_t status = NR_ERROR_NONE;
    nr_channel_manager& channel_manager = m_object_manager.channel_manager();

    // Start each device data
    foreach(nr_device_id_t device_id, request_obj.item_list())
    {
        // Device already in use, no need to start it again
        if (session->channels().contains(device_id))
            continue;
        
        // Try to create an output channel
        nr_channel_output *channel_output = NULL;
        status = channel_manager.create_output_channel(device_id, *session, channel_output);
        if (NR_FAILED(status))
        {
            reply_obj.failed_item_list().push_back(device_id);
            continue;
        }
        Q_ASSERT(channel_output);

        // Add to the channel list before start it, because 
        // Start channel may start a new thread and some data my come immediately,
        // and we should not miss the data
        session->channels().insert(device_id, channel_output);
        status = channel_output->start();
        if (NR_FAILED(status))
        {
            session->channels().remove(device_id);
            channel_manager.destroy_output_channel(channel_output);
            
            reply_obj.failed_item_list().push_back(device_id);
            continue;
        }
    }
    
    // Remove the session if it is empty
    remove_session_if_empty(session);

    return NR_ERROR_NONE;
}

nr_error_t nr_net_server_logic::on_stop_data(nr_net_socket &socket, nr_object &request, nr_object &reply)
{
    nr_request_stop_data& request_obj = request.object_ref<nr_request_stop_data>();
    nr_reply_stop_data& reply_obj = reply.object_ref<nr_reply_stop_data>();
    
    // Find the session first
    nr_net_sessions::iterator it = m_sessions.find(socket.session_id());
    if (it == m_sessions.end())
        return NR_ERROR_CHANNEL_NOT_FOUND;
    
    // Remove each device output
    nr_net_session* session = it.value();
    Q_ASSERT(session);
    for(QList<nr_device_id_t>::iterator device_it = request_obj.item_list().begin();
        device_it != request_obj.item_list().end(); ++device_it)
    {
        session->stop_device(*device_it);
    }
    
    // Remove the session if it is empty
    remove_session_if_empty(session);
    
    return NR_ERROR_NONE;
}

nr_error_t nr_net_server_logic::on_query(nr_net_socket &socket, nr_object &request, nr_object &reply)
{
    m_io_manager.load();

    nr_request_query_data& request_obj = request.object_ref<nr_request_query_data>();
    nr_reply_query_data& reply_obj = reply.object_ref<nr_reply_query_data>();

    m_io_manager.io()->query_data( request_obj.id() , request_obj.begin_time() , 
        request_obj.end_time() , reply_obj.data_list() );
    
    return NR_ERROR_NONE;
    //return NR_ERROR_NOT_IMPLEMENTED;
}

void nr_net_server_logic::close_session(nr_id_t& session_id)
{
    // Find the session id in the session list and destroy it then
    nr_net_sessions::iterator it = m_sessions.find(session_id);
    if (it != m_sessions.end()) // 
    {
        m_sessions.erase(it);
        delete it.value();
    }
}

void nr_net_server_logic::remove_session_if_empty(nr_net_session *session)
{
    Q_ASSERT(session);
    if (session->is_empty())
    {
        m_sessions.remove(session->session_id());
        delete session;
    }
}

